package ca.polymtl.rubikcube.CameraActivity;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Point;
import android.hardware.Camera;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;
import ca.polymtl.rubikcube.R;
import ca.polymtl.rubikcube.ColorCorrectionActivity.ColorCorrectionActivity;
import ca.polymtl.rubikcube.util.Color;

public class CameraActivity extends Activity implements PictureCallback, PreviewCallback {
	final private static String LOG_TAG = CameraActivity.class.getSimpleName();

	CameraSurfaceView cameraSurfaceView;
	OverlayView overlayView;
	int cubeColors[] = new int[54];
	int currentFace = 0;
	TextView statusText;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		Log.d(LOG_TAG, "onCreate");

		Window window = this.getWindow();
		window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

		setContentView(R.layout.camera_layout);

		cameraSurfaceView = (CameraSurfaceView) findViewById(R.id.cameraSurface);
		overlayView = (OverlayView) findViewById(R.id.overlayView);
		statusText = (TextView) findViewById(R.id.statusText);

	}

	/* From Activity */
	@Override
	protected void onPause() {
		super.onPause();

		Log.d(LOG_TAG, "onPause");
	}

	@Override
	protected void onResume() {
		super.onResume();

		Log.d(LOG_TAG, "onResume");
	}

	public void takePicture(View v) {
		cameraSurfaceView.getCamera().setOneShotPreviewCallback(this);
		// cameraSurfaceView.getCamera().takePicture(null, null, this);
		cameraSurfaceView.getCamera().startPreview();
	}

	public void onPictureTaken(byte[] data, Camera camera) {
		if (data != null) {

			Log.d(LOG_TAG, "Picture taken in format " + camera.getParameters().getPreviewFormat() + ", " + data.length
					+ " bytes, " + camera.getParameters().getPreviewSize().width + "x"
					+ camera.getParameters().getPreviewSize().height + " pixels");

		} else {
			Log.d(LOG_TAG, "Preview data is null");
		}
	}

	int clip(int a) {
		if (a > 255) {
			return 255;
		} else if (a < 0) {
			return 0;
		} else {
			return a;
		}
	}

	private final class RGB {
		public RGB(int r, int g, int b) {
			this.r = r;
			this.g = g;
			this.b = b;
		}

		@Override
		public String toString() {
			return "RGB [r=" + r + ", g=" + g + ", b=" + b + "]";
		}

		final public int r, g, b;
	}

	private RGB conv_yuy2(int y, int u, int v) {
		int C, D, E, R, G, B;

		y = y & 0x000000ff;
		u = u & 0x000000ff;
		v = v & 0x000000ff;

		C = y - 16;
		D = u - 128;
		E = v - 128;

		R = clip((298 * C + 409 * E + 128) >> 8);
		G = clip((298 * C - 100 * D - 208 * E + 128) >> 8);
		B = clip((298 * C + 516 * D + 128) >> 8);

		return new RGB(R, G, B);
	}

	private RGB findPixelValue(byte[] data, int imageWidth, Point p) {
		int n = p.y * 848 + p.x;
		n = n * 2;

		// Log.d(LOG_TAG, "Point p = " + p + ", found at index " + n);

		int y = data[n];

		n = n & 0xfffffffc;

		int u = data[n + 1];
		int v = data[n + 3];

		return conv_yuy2(y, u, v);
	}

	// top is smaller than bottom, left is smaller than right
	private RGB averageSquare(byte[] data, int imageWidth, int top, int left, int size) {
		Point p0 = new Point(left + size / 3, top + size / 3);
		Point p1 = new Point(left + size / 3, top + 2 * size / 3);
		Point p2 = new Point(left + 2 * size / 3, top + 2 * size / 3);
		Point p3 = new Point(left + 2 * size / 3, top + size / 3);

		RGB rgb0 = findPixelValue(data, imageWidth, p0);
		RGB rgb1 = findPixelValue(data, imageWidth, p1);
		RGB rgb2 = findPixelValue(data, imageWidth, p2);
		RGB rgb3 = findPixelValue(data, imageWidth, p3);

		int r, g, b;

		r = rgb0.r + rgb1.r + rgb2.r + rgb3.r;
		g = rgb0.g + rgb1.g + rgb2.g + rgb3.g;
		b = rgb0.b + rgb1.b + rgb2.b + rgb3.b;

		final RGB rgb = new RGB(r / 4, g / 4, b / 4);

		// Log.d(LOG_TAG, "Average of (" + left + "," + top + ") to (" + (left + size) + "," + (top + size) + ") is "
		// + rgb);

		return rgb;
	}

	public void onPreviewFrame(byte[] data, Camera camera) {
		if (data != null) {
			Size previewSize = camera.getParameters().getPreviewSize();
			int size = data.length;

			Log.d(LOG_TAG, "Preview taken in format " + camera.getParameters().getPreviewFormat() + ", " + size
					+ " bytes, " + previewSize.width + "x" + previewSize.height + " pixels");

			Log.d(LOG_TAG, "Size is " + size);
			int midw = previewSize.width / 2;
			int midh = previewSize.height / 2;

			int cubeSize = (int) (previewSize.height * 0.9);
			int cubeletSize = cubeSize / 3;

			Log.d(LOG_TAG, "cubeSize = " + cubeSize + ", cubeletSize = " + cubeletSize);

			int top = midh - cubeSize / 2;
			int left = midw - cubeSize / 2;

			RGB rgbs[] = new RGB[9];
			int colors[] = new int[9];

			for (int j = 0; j < 3; j++) {
				for (int i = 0; i < 3; i++) {
					rgbs[j * 3 + i] = averageSquare(data, previewSize.width, top + j * cubeletSize, left + i
							* cubeletSize, cubeletSize);
				}
			}

			for (int j = 0; j < 3; j++) {
				for (int i = 0; i < 3; i++) {
					colors[j * 3 + i] = guessColor(rgbs[j * 3 + i]).id;
				}
			}

			Intent intent = new Intent(this, ColorCorrectionActivity.class);
			intent.putExtra("ca.polymtl.rubikcube.colors", colors);

			startActivityForResult(intent, 34);

			/*
			 * AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setCancelable(false);
			 * builder.setNeutralButton("Aight", null); builder.setMessage(lol); AlertDialog create = builder.create();
			 * create.show();
			 */
		} else {
			Log.d(LOG_TAG, "Preview data is null");
		}
	}

	private RGB adjustColor(RGB rgb) {
		int r, g, b;

		r = rgb.r;
		g = rgb.g;
		b = rgb.b;

		// int minimum = Math.min(r, Math.min(g, b));
		int maximum = Math.max(r, Math.max(g, b));

		/*
		 * r -= minimum; g -= minimum; b -= minimum;
		 * 
		 * r *= 255; g *= 255; b *= 255;
		 * 
		 * r /= (maximum - minimum); g /= (maximum - minimum); b /= (maximum - minimum);
		 */

		if (maximum != 0) {
			r = r * 255 / maximum;
			b = b * 255 / maximum;
			g = g * 255 / maximum;
		}
		return new RGB(r, g, b);
	}

	private Color guessColor(RGB rgb) {
		int diff[] = new int[6];
		int min = Integer.MAX_VALUE;
		int minIdx = 0;

		rgb = adjustColor(rgb);

		Log.d(LOG_TAG, "RGB color: " + rgb);

		for (int i = 0; i < 6; i++) {
			Color color = Color.getColorById(i);
			Log.d(LOG_TAG, "    Comparing with " + color);

			int diffr = Math.abs(color.red - rgb.r);
			int diffg = Math.abs(color.green - rgb.g);
			int diffb = Math.abs(color.blue - rgb.b);

			diff[i] = diffr * diffr + diffg * diffg + diffb * diffb;

			if (diff[i] < min) {
				minIdx = i;
				min = diff[i];
			}
			Log.d(LOG_TAG, "    Distance from " + color.name + " : " + diff[i]);
		}

		return Color.getColorById(minIdx);
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);

		int[] colorsCorrected = data.getIntArrayExtra("ca.polymtl.rubikcube.colorsCorrected");

		if (colorsCorrected == null) {
			Log.d(LOG_TAG, "No result from color correction activity !");
			return;
		}

		Log.d(LOG_TAG, "Activity result " + requestCode + " " + resultCode);
		Log.d(LOG_TAG, "Colors corrected: ");

		for (int i : colorsCorrected) {
			Log.d(LOG_TAG, "    " + Color.getColorById(i).name);
		}

		for (int i = 0; i < 9; i++) {
			cubeColors[currentFace * 9 + i] = colorsCorrected[i];
		}

		currentFace++;

		statusText.setText("Face #" + (currentFace + 1));

		if (currentFace == 6) {
			Intent intent = new Intent();
			intent.putExtra("ca.polymtl.rubikcube.colorsComplete", cubeColors);
			setResult(RESULT_OK, intent);

			finish();
		}

		return;
	}
}
